package kg.apc.jmeter.gui;

import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;

/**
 *  The ComponentBorder class allows you to place a real component in
 *  the space reserved for painting the Border of a component.
 *
 *  This class takes advantage of the knowledge that all Swing components are
 *  also Containers. By default the layout manager is null, so we should be
 *  able to place a child component anywhere in the parent component. In order
 *  to prevent the child component from painting over top of the parent
 *  component a Border is added to the parent componet such that the insets of
 *  the Border will reserve space for the child component to be painted without
 *  affecting the parent component.
 *
 *  From: http://tips4java.wordpress.com/2009/09/27/component-border/
 */
public class ComponentBorder implements Border {

    public enum Edge {

        TOP,
        LEFT,
        BOTTOM,
        RIGHT
    }
    public static final float LEADING = 0.0f;
    public static final float CENTER = 0.5f;
    public static final float TRAILING = 1.0f;
    private JComponent parent;
    private JComponent component;
    private Edge edge;
    private float alignment;
    private int gap = 5;
    private boolean adjustInsets = true;
    private Insets borderInsets = new Insets(0, 0, 0, 0);

    /**
     *  Convenience constructor that uses the default edge (Edge.RIGHT) and
     *  alignment (CENTER).
     *
     *  @param component the component to be added in the Border area
     */
    public ComponentBorder(JComponent component) {
        this(component, Edge.RIGHT);
    }

    /**
     *  Convenience constructor that uses the default alignment (CENTER).
     *
     *  @param component the component to be added in the Border area
     *  @param edge a valid Edge enum of TOP, LEFT, BOTTOM, RIGHT
     */
    public ComponentBorder(JComponent component, Edge edge) {
        this(component, edge, CENTER);
    }

    /**
     *  Main constructor to create a ComponentBorder.
     *
     *  @param component the component to be added in the Border area
     *  @param edge  a valid Edge enum of TOP, LEFT, BOTTOM, RIGHT
     *  @param alignment the alignment of the component along the
     *                   specified Edge. Must be in the range 0 - 1.0.
     */
    public ComponentBorder(JComponent component, Edge edge, float alignment) {
        this.component = component;
        component.setSize(component.getPreferredSize());
        component.setCursor(Cursor.getDefaultCursor());
        setEdge(edge);
        setAlignment(alignment);
    }

    public boolean isAdjustInsets() {
        return adjustInsets;
    }

    public void setAdjustInsets(boolean adjustInsets) {
        this.adjustInsets = adjustInsets;
    }

    /**
     *  Get the component alignment along the Border Edge
     *
     *  @return the alignment
     */
    public float getAlignment() {
        return alignment;
    }

    /**
     *  Set the component alignment along the Border Edge
     *
     *  @param alignment a value in the range 0 - 1.0. Standard values would be
     *	                 CENTER (default), LEFT and RIGHT.
     */
    public final void setAlignment(float alignment) {
        this.alignment = alignment > 1.0f ? 1.0f : alignment < 0.0f ? 0.0f : alignment;
    }

    /**
     *  Get the Edge the component is positioned along
     *
     *  @return the Edge
     */
    public Edge getEdge() {
        return edge;
    }

    /**
     *	Set the Edge the component is positioned along
     *
     *  @param edge the Edge the component is position on.
     */
    public final void setEdge(Edge edge) {
        this.edge = edge;
    }

    /**
     *  Get the gap between the border component and the parent component
     *
     *  @return the gap in pixels.
     */
    public int getGap() {
        return gap;
    }

    /**
     *  Set the gap between the border component and the parent component
     *
     *  @param gap the gap in pixels (default is 5)
     */
    public void setGap(int gap) {
        this.gap = gap;
    }

//
//  Implement the Border interface
//
    @Override
    public Insets getBorderInsets(Component c) {
        return borderInsets;
    }

    @Override
    public boolean isBorderOpaque() {
        return false;
    }

    /**
     *  In this case a real component is to be painted. Setting the location
     *  of the component will cause it to be painted at that location.
     */
    @Override
    public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
        float x2 = (width - component.getWidth()) * component.getAlignmentX() + x;
        float y2 = (height - component.getHeight()) * component.getAlignmentY() + y;
        component.setLocation((int) x2, (int) y2);
    }

    /*
     *  Install this Border on the specified component by replacing the
     *  existing Border with a CompoundBorder containing the original Border
     *  and our ComponentBorder
     *
     *  This method should only be invoked once all the properties of this
     *  class have been set. Installing the Border more than once will cause
     *  unpredictable results.
     */
    public void install(JComponent parent) {
        this.parent = parent;

        determineInsetsAndAlignment();

        //  Add this Border to the parent

        Border current = parent.getBorder();

        if (current == null) {
            parent.setBorder(this);
        } else {
            CompoundBorder compound = new CompoundBorder(current, this);
            parent.setBorder(compound);
        }

        //  Add component to the parent

        parent.add(component);
    }

    /**
     *	The insets need to be determined so they are included in the preferred
     *  size of the component the Border is attached to.
     *
     *  The alignment of the component is determined here so it doesn't need
     *  to be recalculated every time the Border is painted.
     */
    private void determineInsetsAndAlignment() {
        borderInsets = new Insets(0, 0, 0, 0);

        //  The insets will only be updated for the edge the component will be
        //  diplayed on.
        //
        //  The X, Y alignment of the component is controlled by both the edge
        //  and alignment parameters

        if (edge == Edge.TOP) {
            borderInsets.top = component.getPreferredSize().height + gap;
            component.setAlignmentX(alignment);
            component.setAlignmentY(0.0f);
        } else if (edge == Edge.BOTTOM) {
            borderInsets.bottom = component.getPreferredSize().height + gap;
            component.setAlignmentX(alignment);
            component.setAlignmentY(1.0f);
        } else if (edge == Edge.LEFT) {
            borderInsets.left = component.getPreferredSize().width + gap;
            component.setAlignmentX(0.0f);
            component.setAlignmentY(alignment);
        } else if (edge == Edge.RIGHT) {
            borderInsets.right = component.getPreferredSize().width + gap;
            component.setAlignmentX(1.0f);
            component.setAlignmentY(alignment);
        }

        if (adjustInsets) {
            adjustBorderInsets();
        }
    }

    /*
     *  The complimentary edges of the Border may need to be adjusted to allow
     *  the component to fit completely in the bounds of the parent component.
     */
    private void adjustBorderInsets() {
        Insets parentInsets = parent.getInsets();

        //  May need to adust the height of the parent component to fit
        //  the component in the Border

        if (edge == Edge.RIGHT || edge == Edge.LEFT) {
            int parentHeight = parent.getPreferredSize().height - parentInsets.top - parentInsets.bottom;
            int diff = component.getHeight() - parentHeight;

            if (diff > 0) {
                int topDiff = (int) (diff * alignment);
                int bottomDiff = diff - topDiff;
                borderInsets.top += topDiff;
                borderInsets.bottom += bottomDiff;
            }
        }

        //  May need to adust the width of the parent component to fit
        //  the component in the Border

        if (edge == Edge.TOP || edge == Edge.BOTTOM) {
            int parentWidth = parent.getPreferredSize().width - parentInsets.left - parentInsets.right;
            int diff = component.getWidth() - parentWidth;

            if (diff > 0) {
                int leftDiff = (int) (diff * alignment);
                int rightDiff = diff - leftDiff;
                borderInsets.left += leftDiff;
                borderInsets.right += rightDiff;
            }
        }
    }
}
